﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CopyByteChangedFile
{
    class Program
    {
        #region declarations
        private static string _strSourceFileName = "";
        private static string _strDestination = "";

        private enum enmSourceType
        {
            File = 1,
            Directory
        }
        #endregion



        #region MAIN
        static void Main(string[] args)
        {
            #region debugging tests
#if DEBUG
            //string strPath = @"c:\totalcmd801a\TOTALCMD64.EXE";
            //if (Directory.Exists(strPath))
            //{
            //    Console.WriteLine(strPath + " is a directory");
            //}
            //if (File.Exists(strPath))
            //{
            //    Console.WriteLine(strPath + " is a file");
            //}

            //strPath = @"c:\totalcmd801a";
            //if (Directory.Exists(strPath))
            //{
            //    Console.WriteLine(strPath + " is a directory");
            //}
            //if (File.Exists(strPath))
            //{
            //    Console.WriteLine(strPath + " is a file");
            //}


            //int i = 0;
            //foreach (string arg in args)
            //{
            //    Console.WriteLine("args[" + i + "]: " + args[i++] + "|");
            //}

            //string[] paths = args[0].Trim().Split('\\');
            //string path = "";
            //for (i = paths.Count() - 1; i >= 0; i--)
            //{
            //    path = paths[i];
            //    if (!string.IsNullOrEmpty(path)) { break; }
            //}

            //Console.WriteLine(path);

            //Console.WriteLine(Path.GetPathRoot(args[0].Trim()));


            //Console.ReadKey();
            //return;
#endif
            #endregion
            //check arguments
            int intArgumentCount = args.Count();
            enmSourceType estSource = enmSourceType.File;

            if (intArgumentCount >= 1)
            {
                _strSourceFileName = args[0].Trim();
                if (Directory.Exists(_strSourceFileName)) { estSource = enmSourceType.Directory; }
            }
            if (intArgumentCount >= 2)
            {
                _strDestination = args[1].Trim() + "\\";
                //appending a backslash makes sure that Path.GetDirectoryName returns the correct path.
                //example:
                //Destination path should be c:\temp
                //Path.GetDirectoryName(@"c:\temp") = "c:\"
                //Path.GetDirectoryName(@"c:\temp\") = "c:\temp"
                //Path.GetDirectoryName(@"c:\temp\\") = "c:\temp"
                //Path.GetDirectoryName(@"c:\temp\\\") = "c:\temp"
                //so you can't have too much \ but you can really miss one :)

                //cleanup destination
                _strDestination = Path.GetDirectoryName(_strDestination);

                //append destination dir in case of directory copy
                if (estSource == enmSourceType.Directory)
                {
                    _strDestination += "\\" + _getSingleDestinationDirectoryName(_strSourceFileName);
                    //create it if not existent
                    if (!Directory.Exists(_strDestination)) { Directory.CreateDirectory(_strDestination); }
                }
            }

            //evaluate arguments
            if (intArgumentCount == 0 ||
                _strSourceFileName == "/?")
            {
                _printHelp();
            }
            else if (_sourceIsValid() &&
                _destinationIsValid())
            {
                using (CopyHandler ch = new CopyHandler())
                {
                    //subscribe
                    ch.FileCopyAction += ch_FileCopyAction;
                    ch.FileCopyException += ch_FileCopyException;
                    ch.FileCopyNoAction += ch_FileCopyNoAction;
                    ch.FileCopySuccessful += ch_FileCopySuccessful;

                    //This is the only place where a copy operation can occur
                    if (estSource == enmSourceType.Directory)
                    {
                        _copyDirectoryStructure(ch);
                    }
                    else
                    {
                        _copySingleFile(ch);
                    }

                    //unsubscribe
                    ch.FileCopyAction -= ch_FileCopyAction;
                    ch.FileCopyException -= ch_FileCopyException;
                    ch.FileCopyNoAction -= ch_FileCopyNoAction;
                    ch.FileCopySuccessful -= ch_FileCopySuccessful;
                }
            }
            else
            {
                //generate error messages
                if (!File.Exists(_strSourceFileName))
                {
                    Console.WriteLine("Quelldatei nicht gefunden.");
                }
                if (!Directory.Exists(_strDestination))
                {
                    Console.WriteLine("Zielverzeichnis existiert nicht.");
                }
            }

#if DEBUG
            Console.ReadKey();
#endif
        }
        #endregion



        #region sub functions
        private static bool _sourceIsValid()
        {
            if (!string.IsNullOrEmpty(_strSourceFileName) &&
                (File.Exists(_strSourceFileName) || Directory.Exists(_strSourceFileName)))
            {
                return true;
            }

            return false;
        }

        private static bool _destinationIsValid()
        {
            if (!string.IsNullOrEmpty(_strDestination) &&
               Directory.Exists(_strDestination))
            {
                return true;
            }

            return false;
        }

        private static string _getSingleDestinationDirectoryName(
            string SourceFullPath)
        {
            string[] paths = SourceFullPath.Trim().Split('\\');
            string path = "";
            for (int i = paths.Count() - 1; i >= 0; i--)
            {
                path = paths[i];
                if (!string.IsNullOrEmpty(path)) { break; }
            }

            if (path + "\\" == Path.GetPathRoot(_strSourceFileName)) { return ""; }

            return path;
        }

        private static void _copySingleFile(
            CopyHandler ch)
        {
            ch.ExecuteFileCopyAction(_strSourceFileName, _strDestination);
        }

        private static void _copyDirectoryStructure(
            CopyHandler ch)
        {
            _copyDirectory(ch, _strSourceFileName);
        }

        private static void _copyDirectory(
            CopyHandler ch,
            string DirectoryName)
        {
            //get files and subdirectories
            string[] SourceFileNames = Directory.GetFiles(DirectoryName);
            string[] SourceSubDirectoryNames = Directory.GetDirectories(DirectoryName);

            //first off, copy directories
            foreach (string SourceSubDirectoryName in SourceSubDirectoryNames)
            {
                //check if destination directory exits. if not, create it.
                string strDestinationSubDirectoryName = SourceSubDirectoryName.Replace(_strSourceFileName, _strDestination + "\\");
                if (!Directory.Exists(strDestinationSubDirectoryName)) { Directory.CreateDirectory(strDestinationSubDirectoryName); }

                //cycle through next dir
                _copyDirectory(ch, SourceSubDirectoryName);
            }

            //now to copy all files
            string strDestinationDir = DirectoryName.Replace(_strSourceFileName, _strDestination );
            foreach (string SourceFileName in SourceFileNames)
            {
                ch.ExecuteFileCopyAction(SourceFileName, strDestinationDir);
            }
        }
        #endregion



        #region output
        private static void _printHelp()
        {
            string strHelp = string.Concat(
                "Kopiert die angegebene Datei nur wenn die Datei am Zielort entweder nicht vorhanden ist, oder eine andere Dateigröße hat.",
                Environment.NewLine, Environment.NewLine,
                "CopyByteChangedFile [/?] [Dateiname] [Zielverzeichnis]", Environment.NewLine,
                "[/?]              - zeigt diese Hilfe an.", Environment.NewLine,
                "[Dateiname]       - Quelldatei. CopyByteChangedFile überprüft ob die Datei vorhanden ist.", Environment.NewLine,
                "[Zielverzeichnis] - Kopierziel. CopyByteChangedFile überprüft ob das Verzeichnis existiert und ob die Quelldatei dort vorhanden ist.", Environment.NewLine,
                Environment.NewLine,
                "Wenn die Quelldatei und das Zielverzeichnis existieren, überprüft CopyByteChangedFile ob die Quelldatei im Zielverzeichnis existiert. Wenn nicht, wird sie einfach kopiert. Wenn ja, vergleicht CopyByteChangedFile die Dateigrößen und kopiert nur bei unterschiedlicher Größe.", Environment.NewLine);
            Console.WriteLine(strHelp);
        }

        static void ch_FileCopyNoAction(object sender, FileCopyActionEventArgs e)
        {
            Console.WriteLine("Keine Aktion notwendig für: " + e.SourceFile.FullFileName);
        }

        static void ch_FileCopyAction(object sender, FileCopyActionEventArgs e)
        {
            Console.WriteLine("Kopiere: '" + e.SourceFile.FullFileName + "' -> '" + e.DestinationFile.FullFileName + "'");
        }

        static void ch_FileCopyException(object sender, FileCopyExceptionEventArgs e)
        {
            Console.WriteLine("Fehler beim Kopieren: '" + e.SourceFile.FullFileName + "' -> '" + e.DestinationFile.FullFileName + "'");
            Console.WriteLine("Fehlermeldung: " + e.FileCopyException.Message);
        }

        static void ch_FileCopySuccessful(object sender, FileCopyActionEventArgs e)
        {
            Console.WriteLine("Kopieren erfolgreich: '" + e.SourceFile.FullFileName + "' -> '" + e.DestinationFile.FullFileName + "'");
        }
        #endregion



        #region obsolete
        /// <summary>
        /// obsolete since CopyHandler is active now
        /// </summary>
        private static void _copyAction()
        {
            using (CopyHandler ch = new CopyHandler())
            {
            }

            //get source file size since source file existance was checked already
            long lngSourceFileSize = (new FileInfo(_strSourceFileName)).Length;
            long lngDestinationFileSize = 0;

            //put together destination file name and get its file size
            string strDestinationFileName = _strDestination + "\\" + Path.GetFileName(_strSourceFileName);
            if (File.Exists(strDestinationFileName))
            {
                lngDestinationFileSize = (new FileInfo(strDestinationFileName)).Length;
            }

            //message
            Console.WriteLine("Kopiere " + _strSourceFileName + " (" + lngSourceFileSize + " bytes)");

            //file size differs no matter if file does not exist or it really is different
            if (lngSourceFileSize != lngDestinationFileSize)
            {
                //copy NOW!
                try
                {
                    File.Copy(_strSourceFileName, strDestinationFileName, true);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Fehler bei Kopiervorgang. Datei wird nicht kopiert.");
                    Console.WriteLine("Quelle    : " + _strSourceFileName);
                    Console.WriteLine("Ziel      : " + _strDestination);
                    Console.WriteLine("ex.Message: " + ex.Message);
                }

                //report 
                Console.WriteLine("Kopiervorgang erfolgreich.");
                Console.WriteLine("Quelle    : " + _strSourceFileName + " (" + lngSourceFileSize + " bytes)");
                Console.WriteLine("Ziel      : " + _strDestination);
                Console.WriteLine(Environment.NewLine);
            }
            else
            {
                //report 
                Console.WriteLine("Keine Aktion notwendig.");
                Console.WriteLine("Quelle    : " + _strSourceFileName + " (" + lngSourceFileSize + " bytes)");
                Console.WriteLine("Ziel      : " + strDestinationFileName + " (" + lngDestinationFileSize + " bytes)");
                Console.WriteLine(Environment.NewLine);
            }
        }
        #endregion
    }
}
